/*********************************************************************
* File: Agent.cpp
*
* \brief Implements the agent.
*
* \author Instituto Superior Tecnico de Lisboa, Portugal
* \author Technical University of Lisbon, Portugal
* 
* \author Agentes Autonomos e Sistemas Multiagente
* \author Autonomous Agents and Multiagent Systems
* 
* \version	1.0
* \date		2006/2007
*********************************************************************/
#include "Agent.h"
using namespace agents;
#include "../linkGame.h"
#include "../metaApi.h"
#include "../util/mathLibrary.h"
#include "../util/util.h"
#include "../util/debug.h"
using namespace util;
#include "../game/waypoint.h"
#include "../game/search.h"
#include "../game/Tournament.h"
#include "modules/WaypointNavigation.h"
#include "modules/Communication.h"
#include "modules/Finances.h"
#include "modules/Combat.h"
#include "modules/Senses.h"

int Agent::counter = 0;

/**
*
**/
void player( entvars_t *pev ) 
{
	static LINK_ENTITY_GAME otherClassName = NULL;
	if (otherClassName == NULL)
		otherClassName = (LINK_ENTITY_GAME)GetProcAddress(h_Library, "player");
	if (otherClassName != NULL)
		(*otherClassName)(pev);
}

/**
* \brief Gets the agent class from the associated Half-Life engine entity
*
* \param[in] pent the entity
**/
Agent *Agent::getInstance(edict_t *pent)
{
	if (FNullEnt(pent))
		return NULL;

	int index = ENTINDEX(pent) - 1;
	if (index < 0 || index >= 32)
		return NULL;

	return g_world->agents[index];
}

/**
* \brief The class constructor.
*
* \param[in] entity The Half-Life engine entity.
* \param[in] team The team.
* \param[in] model The geometry model.
**/
Agent::Agent(edict_t *entity, int team, int model)
{
	char ptr[128];  // Allocate space for message from ClientConnect
	char *infobuffer;
	int clientIndex = ENTINDEX(entity);

	memset(this, 0, sizeof(Agent));

	pev = VARS(entity);

	if (entity->pvPrivateData != NULL)
		FREE_PRIVATE(entity);
	entity->pvPrivateData = NULL;
	entity->v.frags = 0;

	// Create the player entity by calling MOD's player function
	if (g_bIsMMPlugin)
		CALL_GAME_ENTITY(PLID, "player", &entity->v);
	else
		player(VARS(entity));

	// Set all infobuffer keys for this agent
	infobuffer = GET_INFOKEYBUFFER( entity );

	SET_CLIENT_KEYVALUE(clientIndex, infobuffer, "model", "");
	SET_CLIENT_KEYVALUE(clientIndex, infobuffer, "rate", "3500.000000");
	SET_CLIENT_KEYVALUE(clientIndex, infobuffer, "cl_updaterate", "20");
	SET_CLIENT_KEYVALUE(clientIndex, infobuffer, "cl_lw", "1");
	SET_CLIENT_KEYVALUE(clientIndex, infobuffer, "cl_lc", "1");
	SET_CLIENT_KEYVALUE(clientIndex, infobuffer, "tracker", "0");
	SET_CLIENT_KEYVALUE(clientIndex, infobuffer, "cl_dlmax", "128");
	SET_CLIENT_KEYVALUE(clientIndex, infobuffer, "friends", "0");
	SET_CLIENT_KEYVALUE(clientIndex, infobuffer, "dm", "0");
	if (g_world->isVersion16)
		SET_CLIENT_KEYVALUE(clientIndex, infobuffer, "_vgui_menus", "0");

	MDLL_ClientConnect(entity, "agent", "127.0.0.1", ptr);
	MDLL_ClientPutInServer(entity);

	// Initialize all the variables for this agent..
	started = false;  // Hasn't joined game yet

	startAction = GameStartActions::idle;

	_isAlive = false;

	entity->v.idealpitch = entity->v.v_angle.x;
	entity->v.ideal_yaw = entity->v.v_angle.y;
	entity->v.yaw_speed = 0;
	entity->v.pitch_speed = 0;

	// Assign team and class
	wantedTeam = team;
	wantedClass = model;

	_id = counter++;

	util::convertNameToHuman(STRING(getEdict()->v.netname), _name);

	navigation = new WaypointNavigation(*this);
	communication = new Communication(*this);
	finances = new Finances(*this);
	combat = new Combat(*this);
	senses = new Senses(*this);

	newRound();
}


/**
* \brief The class destructor.
**/
Agent::~Agent(){
	if(navigation != 0)
		delete navigation;
	if(communication != 0)
		delete communication;
	if(finances != 0)
		delete finances;
	if(combat != 0)
		delete combat;
	if(senses != 0)
		delete senses;
}

/** 
* \brief Gets the agent's team.
*
* \see agents::IAgent::getTeam
**/
CounterStrikeTeams::Enumeration Agent::getTeam()
{
	return (CounterStrikeTeams::Enumeration) util::getTeam(getEdict());
}

/** 
* \brief Gets the agent's health.
*
* \see agents::IAgent::hasTakenDamage
**/
int Agent::getHealth()
{
	return pev->health;
}

/** 
* \brief Whether the agent has taken damage in the previous cycle.
*
* \see agents::IAgent::hasTakenDamage
**/
bool Agent::hasTakenDamage()
{
	return (pev->dmg_take > 0);
}

/** 
* \brief Team and model selection procedure at the start of the game.
**/
void Agent::startGame(){
	// handle Counter-Strike stuff here...
	if (startAction == GameStartActions::teamSelect)
	{
		startAction = GameStartActions::idle;  // switch back to idle

		if (wantedTeam != 1 && wantedTeam != 2)
			wantedTeam = 5; //random team selection

		//Select the team the agent wishes to join
		util::fakeClientCommand(getEdict(), "menuselect %d", wantedTeam);
	}
	else if (startAction == GameStartActions::classSelect)
	{
		startAction = GameStartActions::idle;  // switch back to idle

		if (wantedClass < 1 || wantedClass > 4)
			wantedClass = RANDOM_LONG(1, 4);  // use random if invalid

		//Select the class the agent wishes to use
		util::fakeClientCommand(getEdict(), "menuselect %d", wantedClass);

		//If in an assassination map, determine whether the agent is the VIP
		if (g_world->getMapType() & MapTypes::assassination)
		{
			char *infobuffer = g_engfuncs.pfnGetInfoKeyBuffer( getEdict() );
			if (*(g_engfuncs.pfnInfoKeyValue(infobuffer, "model")) == 'v')
				isVIP = true;
		}

		//Agent has now joined the game (doesn't need to be started)
		started = true;
	}
}

/** 
* \brief Starts a new round.
*
* Should be invoked whenever a new round starts.
**/
void Agent::newRound()
{
	isVIP = false;
	lastDamageType = -1; // Reset damage
	spawnTime = gpGlobals->time;
	pev->v_angle.y = pev->ideal_yaw;

	navigation->newRound();
	communication->newRound();
	finances->newRound();
	combat->newRound();
	senses->newRound(); 

	_startAimTargetSet = false;
}

/** 
* \brief The agent's decision cycle. 
*
* This is were agent behaviour should be defined. 
* This function should be invoked every frame. 
**/
void Agent::main()
{
	try{
		_doThink = true;
		_doPostThink = true;

		preThink();
		if(_doThink)
			think();
		if(_doPostThink)
			postThink();
		g_tournament.update();
	}
	catch(...)
	{
		if(g_tournament.isOn()){
			if(g_tournament.isTournamentMaster())
				g_tournament.handleException(true);
			else
				getCommunication().sayText("_TOURNAMENT_EXCEPTION_");
		}
		else
			throw "Exception!";
	}
}

/** 
* \brief Prepares a decision cycle. 
*
* Should be invoked before 'think'.
**/
void Agent::preThink()
{
	pev->button = 0;
	_isAlive = util::isAlive(getEdict());
	if(_isAlive)
		navigation->startCycle();
	if(!_isAlive)
		_doThink = false;

	if(!started){
		startGame();
		_doThink = false;
	}
	else if(!_startAimTargetSet){
		Vector defaultTarget; //(0, 0, 0)
		combat->aimAtInstantaneously(defaultTarget);
		_startAimTargetSet = true;
	}
}

/** 
* \brief The core of the decision cycle. 
*
* Specific agents should override this function to define their behaviors
**/
void Agent::think()
{
}

/** 
* \brief Concludes the decision cycle. 
*
* Should be invoked after 'think'
**/
void Agent::postThink()
{
	if(_isAlive)
	{
		navigation->endCycle();
		combat->endCycle();
	}

	Vector angles = getNavigation().getAngles();
	float frontSpeed = navigation->getEffectiveFrontSpeed();
	float sideSpeed = navigation->getEffectiveSideSpeed();

	g_engfuncs.pfnRunPlayerMove( getEdict(),
		angles, frontSpeed, sideSpeed, 0, 
		pev->button, pev->impulse, g_msecVal);
}

/**
* \brief Whether reaching a certain target location would hurt the agent
* with falling damage.
*
* \param[in] target The target.
**/
bool Agent::isDeadlyDrop(Vector target)
{
	Vector vecOrigin = pev->origin;

	TraceResult tr;

	Vector vecMove;
	vecMove.y = util::math::vectorToYaw(target - vecOrigin);
	vecMove.x = 0; // reset pitch to 0 (level horizontally)
	vecMove.z = 0; // reset roll to 0 (straight up and down)
	util::math::makeVectors( vecMove );

	Vector v_direction = (target - vecOrigin).Normalize();  // 1 unit long
	Vector v_check = vecOrigin;
	Vector v_down = vecOrigin;

	v_down.z = v_down.z - 1000.0;  // straight down 1000 units

	util::traceHull(v_check, v_down, ignore_monsters, head_hull, getEdict(), &tr);

	if (tr.flFraction > 0.036) // We're not on ground anymore?
		tr.flFraction = 0.036;

	float height;
	float last_height = tr.flFraction * 1000.0;  // height from ground

	float distance = (target - v_check).Length();  // distance from goal

	while (distance > 16.0)
	{
		v_check = v_check + v_direction * 16.0; // move 10 units closer to the goal...

		v_down = v_check;
		v_down.z = v_down.z - 1000.0;  // straight down 1000 units

		util::traceHull(v_check, v_down, ignore_monsters, head_hull, getEdict(), &tr);

		if (tr.fStartSolid) // Wall blocking?
			return false;

		height = tr.flFraction * 1000.0;  // height from ground

		if (last_height < height - 100) // Drops more than 100 Units?
			return true;

		last_height = height;

		distance = (target - v_check).Length();  // distance from goal
	}

	return false;
}



/**
* \brief Whether there is a wall to the left.
*
* \param[in] distanceToTheLeft Distance to check the existence of a wall. 
*		Defaults to 40 units.
**/
bool Agent::isWallOnLeft(int distanceToTheLeft)
{
	Vector v_src, v_left;
	TraceResult tr;

	util::math::makeVectors( pev->angles );

	// do a trace to the left...
	v_src = pev->origin;
	v_left = v_src - gpGlobals->v_right * distanceToTheLeft;

	util::traceline( v_src, v_left, ignore_monsters, getEdict(), &tr);

	// check if the trace hit something...
	if (tr.flFraction < 1.0)
		return TRUE;

	return FALSE;
}


/**
* \brief Whether there is a wall to the right.
*
* \param[in] distanceToTheRight distance to check the existence of a wall. 
*		Defaults to 40 units.
**/
bool Agent::isWallOnRight(int distanceToTheRight)
{
	Vector v_src, v_right;
	TraceResult tr;

	util::math::makeVectors( pev->angles );

	// do a trace to the right...
	v_src = pev->origin;
	v_right = v_src + gpGlobals->v_right * distanceToTheRight;

	util::traceline( v_src, v_right, ignore_monsters, getEdict(), &tr);

	// check if the trace hit something...
	if (tr.flFraction < 1.0)
		return TRUE;

	return FALSE;
}



/**
* \brief Whether the agent is on a ladder.
**/
bool Agent::isOnLadder(void) 
{ 
	return pev->movetype == MOVETYPE_FLY; 
}
/**
* \brief Whether the agent is in water.
**/
bool Agent::isInWater(void) 
{ 
	return pev->waterlevel >= 2; 
}
/**
* \brief Whether agent can duck under an obstacle.
**/
bool Agent::canDuckUnder( Vector vNormal )
{
	TraceResult tr;
	Vector v_baseheight;

	// use center of the body first...
	if (pev->flags & FL_DUCKING)
		v_baseheight = pev->origin + Vector(0, 0, -17);
	else
		v_baseheight = pev->origin;

	// trace a hull forward at duck height...
	util::traceHull( v_baseheight, v_baseheight + vNormal * 32,
		ignore_monsters, head_hull, getEdict(), &tr);

	return (tr.flFraction >= 1.0);
}
/**
* \brief Check if agent can jump over some obstacle.
**/
bool Agent::canJumpUp( Vector vNormal )
{
	TraceResult tr;
	Vector v_jump, v_source, v_dest;

	// Can't jump if not on ground and not on ladder/swimming 
	if (!(pev->flags & FL_ONGROUND) && (isOnLadder() || !isInWater()))
		return false;

	// Check for normal jump height first...
	v_source = pev->origin + Vector(0, 0, -36 + 45);
	v_dest = v_source + vNormal * 32;

	// trace forward at maximum jump height...
	util::traceHull( v_source, v_dest, ignore_monsters, head_hull, getEdict(), &tr);

	if (tr.flFraction < 1.0)
	{
		// Here we check if a Duck Jump would work...
		// use center of the body first...

		// maximum duck jump height is 62, so check one unit above that (63)
		v_source = pev->origin + Vector(0, 0, -36 + 63);
		v_dest = v_source + vNormal * 32;

		// trace forward at maximum jump height...
		util::traceHull( v_source, v_dest, ignore_monsters, head_hull, getEdict(), &tr);

		if (tr.flFraction < 1.0)
			return false;
	}

	// now trace from jump height upward to check for obstructions...
	v_source = v_dest;
	v_dest.z += 37;

	util::traceHull( v_source, v_dest, ignore_monsters, head_hull, getEdict(), &tr);

	return (tr.flFraction >= 1.0);
}


/**
* \brief Whether agent can strafe to the left
**/
bool Agent::canStrafeLeft( TraceResult *tr )
{
	Vector v_src, v_left;

	util::math::makeVectors( pev->v_angle );
	v_src = pev->origin;
	v_left = v_src - gpGlobals->v_right * 40;

	// trace from the agent's waist straight left...
	util::traceline( v_src, v_left, ignore_monsters, getEdict(), tr);

	// check if the trace hit something...
	if (tr->flFraction < 1.0)
		return false;  // agent's body will hit something

	v_left = v_left + gpGlobals->v_forward * 40;

	// trace from the strafe pos straight forward...
	util::traceline( v_src, v_left, ignore_monsters, getEdict(), tr);

	// check if the trace hit something...
	if (tr->flFraction < 1.0)
		return false;  // agent's body will hit something

	return true;
}

/**
* \brief Whether agent can strafe to the right.
**/
bool Agent::canStrafeRight( TraceResult *tr )
{
	Vector v_src, v_right;

	util::math::makeVectors( pev->v_angle );
	v_src = pev->origin;
	v_right = v_src + gpGlobals->v_right * 40;

	// trace from the agent's waist straight right...
	util::traceline( v_src, v_right, ignore_monsters, getEdict(), tr);

	// check if the trace hit something...
	if (tr->flFraction < 1.0)
		return false;  // agent's body will hit something

	v_right = v_right + gpGlobals->v_forward * 40;

	// trace from the strafe pos straight forward...
	util::traceline( v_src, v_right, ignore_monsters, getEdict(), tr);

	// check if the trace hit something...
	if (tr->flFraction < 1.0)
		return false;  // agent's body will hit something

	return true;
}

/**
* \brief Whether agent can move in its direction (including doors).
**/
bool Agent::canMoveForward( Vector vNormal, TraceResult *tr )
{
	// use some TraceLines to determine if anything is blocking the current
	// path of the agent.
	Vector v_src, v_forward, v_center;

	v_center = pev->angles;
	v_center.z = 0;
	v_center.x = 0;
	util::math::makeVectors(v_center);

	// first do a trace from the agent's eyes forward...
	v_src = eyePosition();
	v_forward = v_src + vNormal * 24;

	// trace from the agent's eyes straight forward...
	util::traceline( v_src, v_forward, ignore_monsters, getEdict(), tr);

	// check if the trace hit something...
	if (tr->flFraction < 1.0)
	{
		if (FClassnameIs(tr->pHit, "func_door_rotating") ||
			FClassnameIs(tr->pHit, "func_door"))
			return true;
		else
			return false;  // agent's head will hit something
	}

	// agent's head is clear, check at shoulder level...
	// trace from the agent's shoulder left diagonal forward to the right shoulder...
	v_src = eyePosition() + Vector(0, 0, -16) - gpGlobals->v_right * 16;
	v_forward = eyePosition() + Vector(0, 0, -16) + gpGlobals->v_right * 16 + vNormal * 24;

	util::traceline( v_src, v_forward, ignore_monsters, getEdict(), tr);

	// check if the trace hit something...
	if (tr->flFraction < 1.0)
	{
		if (!FClassnameIs(tr->pHit, "func_door_rotating") &&
			!FClassnameIs(tr->pHit, "func_door"))
			return false;  // agent's body will hit something
	}

	// agent's head is clear, check at shoulder level...
	// trace from the agent's shoulder right diagonal forward to the left shoulder...
	v_src = eyePosition() + Vector(0, 0, -16) + gpGlobals->v_right * 16;
	v_forward = eyePosition() + Vector(0, 0, -16) - gpGlobals->v_right * 16 + vNormal * 24;

	util::traceline( v_src, v_forward, ignore_monsters, getEdict(), tr);

	// check if the trace hit something...
	if (tr->flFraction < 1.0)
	{
		if (!FClassnameIs(tr->pHit, "func_door_rotating") &&
			!FClassnameIs(tr->pHit, "func_door"))
			return false;  // agent's body will hit something
	}

	// Now check below Waist
	if (pev->flags & FL_DUCKING)
	{
		v_src = pev->origin + Vector(0, 0, -19 + 19);
		v_forward = v_src + Vector(0, 0, 10) + vNormal * 24;

		util::traceline( v_src, v_forward, ignore_monsters, getEdict(), tr);

		// check if the trace hit something...
		if (tr->flFraction < 1.0)
		{
			if (!FClassnameIs(tr->pHit, "func_door_rotating") &&
				!FClassnameIs(tr->pHit, "func_door"))
				return false;  // agent's body will hit something
		}

		v_src = pev->origin;
		v_forward = v_src + vNormal * 24;

		util::traceline( v_src, v_forward, ignore_monsters, getEdict(), tr);

		// check if the trace hit something...
		if (tr->flFraction < 1.0)
		{
			if (!FClassnameIs(tr->pHit, "func_door_rotating") &&
				!FClassnameIs(tr->pHit, "func_door"))
				return false;  // agent's body will hit something
		}
	}
	else
	{
		// Trace from the left Waist to the right forward Waist Pos
		v_src = pev->origin + Vector(0, 0, -17) - gpGlobals->v_right * 16;
		v_forward = pev->origin + Vector(0, 0, -17) + gpGlobals->v_right * 16 + vNormal * 24;

		// trace from the agent's waist straight forward...
		util::traceline( v_src, v_forward, ignore_monsters, getEdict(), tr);

		// check if the trace hit something...
		if (tr->flFraction < 1.0)
		{
			if (!FClassnameIs(tr->pHit, "func_door_rotating") &&
				!FClassnameIs(tr->pHit, "func_door"))
				return false;  // agent's body will hit something
		}

		// Trace from the left Waist to the right forward Waist Pos
		v_src = pev->origin + Vector(0, 0, -17) + gpGlobals->v_right * 16;
		v_forward = pev->origin + Vector(0, 0, -17) - gpGlobals->v_right * 16 + vNormal * 24;

		util::traceline( v_src, v_forward, ignore_monsters, getEdict(), tr);

		// check if the trace hit something...
		if (tr->flFraction < 1.0)
		{
			if (!FClassnameIs(tr->pHit, "func_door_rotating") &&
				!FClassnameIs(tr->pHit, "func_door"))
				return false;  // agent's body will hit something
		}
	}

	return true;  // agent can move forward, return true
}





